#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h> // copy to user
#include <asm/io.h>
#include <linux/dma-mapping.h>
#include "git_version.h"
#include <linux/dma-contiguous.h>
//#include <linux/cma.h>


#define DRIVER_NAME "cma"


#define cma_ADDR    (phys_addr_t) 0x4000000
#define cma_SIZE    (512 * 1024)


static char *version = GIT_VERSION;
module_param(version, charp, S_IRUGO);

void *cma_va = NULL;


static int cma_open(struct inode *inode, struct file *file)
{
    struct device *dev = NULL;
    struct miscdevice *misc_dev = file->private_data;
    char *p;
    dev = misc_dev->this_device;

    pr_info(DRIVER_NAME " open\n");

    cma_va = ioremap(cma_ADDR, cma_SIZE);
    pr_info("cma[%d] PA=%016llx->VA=%16px.\n", 0, cma_ADDR, cma_va);
    p = cma_va;

    memset_io(cma_va, 4, cma_SIZE);
    printk("-> %d\n", p[0]);
    printk("-> %d\n", p[1]);
    printk("-> %d\n", p[2]);
    /*
    if (readb(cma_va) != (unsigned char)i) {
        pr_info("failed, stop!\n");
    }
    */

    return 0;
}

static int cma_release2(struct inode *inode, struct file *file)
{
    pr_info(DRIVER_NAME " close.\n");
    iounmap(cma_va);
    return 0;
}

static ssize_t cma_read(struct file *file, char __user *buf, size_t len, loff_t *offset)
{
    size_t tmplen = len;
    if (*offset >= cma_SIZE) {
        /* read empty */
        pr_info(DRIVER_NAME " read: len=%ld, offset=%lld, EOF.\n", tmplen, *offset);
        return 0;
    }
    if (len > cma_SIZE - *offset) {
        len = cma_SIZE - *offset;
    }
    if (copy_to_user(buf, cma_va + *offset, len) != 0) {
        /* copy failed */
        pr_info(DRIVER_NAME " read: copy to user failed!\n");
        return -EFAULT;
    }
    *offset += len;
    pr_info(DRIVER_NAME " read: len=%ld, offset=%lld, got=%ld\n", tmplen, *offset, len);
    return len;
}

static ssize_t cma_write(struct file *file, const char __user *buf, size_t len, loff_t *offset)
{
    size_t tmplen = len;
    if (*offset >= cma_SIZE) {
        /* write full */
        pr_info(DRIVER_NAME " write: len=%ld, offset=%lld, NOSPACE.\n", len, *offset);
        return -ENOSPC;
    }
    if (len > cma_SIZE - *offset) {
        len = cma_SIZE - *offset;
    }
    if (copy_from_user(cma_va + *offset, buf, len) != 0) {
        /* copy failed */
        pr_info(DRIVER_NAME " write: copy from user failed!\n");
        return -EFAULT;
    }
    pr_info(DRIVER_NAME " write: len=%ld, offset=%lld, got=%ld.\n", tmplen, *offset, len);
    *offset += len;
    return len;
}

static const struct file_operations cma_fops = {
    .owner = THIS_MODULE,
    .open = cma_open,
    .release = cma_release2,
    .read = cma_read,
    .write = cma_write,
};

static struct miscdevice cma_device = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = DRIVER_NAME,
    .fops = &cma_fops,
};

static int __init cma_init(void)
{
    int ret;
    struct page *cma_page;

    ret = misc_register(&cma_device);
    if (ret < 0) {
        pr_err("Failed to register misc device\n");
        return ret;
    }

    cma_page = dma_alloc_from_contiguous(NULL, 1, 0, 0);
    if (!cma_page) {
        pr_info("page alloc failed!\n");
        return 1;
    }
    pr_info("MISC device registered\n");

    return 0;
}

static void __exit cma_exit(void)
{
    misc_deregister(&cma_device);
    pr_info("MISC device unregistered\n");
}


module_init(cma_init);
module_exit(cma_exit);


MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple MISC driver example");
